Aprende que es Shadowing en Javascript en 10 minutos

Shadowing es un concepto que es imposible de evitar. Incluso si no conoces el término es 99.999% seguro que lo hayas implementado alguna vez en tu código.

Si lo traduces al español te queda algo como “sombrear” o “ensombrecer”. Pero probablemente sea mejor entenderlo como “ocultar”.

En términos técnicos se refiere al hecho de hacer que una variable en un scope mayor se vuelva inaccesible. Ocurriendo esto cuando se utiliza una variable con el mismo nombre en un scope menor.

Pero ¡ojo! Hacer shadowing no es una buena práctica, no es un estilo o técnica de programación. Es un concepto, es algo que ocurre y que por lo general deberías evitar.

Lo que debes saber antes

Para entender bien el concepto de shadowing, necesitas entender lo que es el lexical scope en Javascript y como funciona. Si quieres revisarlo o repasarlo puedes aprender acerca de Las 3 partes en las que se divide el Lexical Scope de Javascript.

Sin embargo si tienes prisa por entender que es esto del shadowing te puedo dar la explicación rápida.

El lexical scope se refiere al alcance que tiene una variable en un programa y en JS este se divide en 3 tipos: global, local y block. Una variable no es accessible fuera del scope donde fue declarada.

Diagrama circular de color rojo llamado Global Scope. Globals Scope contiene 3 círculos naranjas llamados Local scope 1,2 y 3. Local Scope 1 contiene 2 circulos amarillos llamados Block Scope 1 y 2. Local Scope 2 contiene un circulo amarillo Block Scope 3. Global Scope tiene un ciruclo amarillo lllamafo Block Scope 4.

Shadowing

Caso Base

Como siempre, un ejemplo te ayudará a visualizarlo mejor.

const exerciseRoutine = {
    description: 'pushups',
    weekDays: [1,3,5]
}

/**
 * Este código salva una rutina de ejercicio para un usuario
 * @param {*} inputRoutine 
 */
function saveRoutine (inputRoutine) {
    console.log('exerciseRoutine = ', exerciseRoutine)
    console.log('inputRoutine = ', inputRoutine)
    
    //TODO: Save routine to database
}

const exampleInputRoutine = {
    description: 'situps',
    weekDays: [1,2,3,4,5]
}
saveRoutine(exampleInputRoutine)

Este código contiene una variable global y una local:

  • exerciseRoutine – variable global
  • inputRoutine – variable local de la función saveRoutine

Como ya sabes la variable global es accesible, desde cualquier lado y porsupuesto la variable local solo desde dentro de su función, por lo que si corres este código se imprimirán ambos logs sin problemas.

exerciseRoutine =  { description: 'pushups', weekDays: [ 1, 3, 5 ] }
inputRoutine =  { description: 'situps', weekDays: [ 1, 2, 3, 4, 5 ] }
Diagrama circular de color rojo llamado Global Scope con 2 variables: exerciseRoutine y saveRoutine. También contiene un localScope(saveRoutine) que a su vez tiene otra variable llamada inputRoutine.

Shadowing en acción

Ahora digamos que decides renombrar el parámetro de inputRoutine a exerciseRoutine, tal vez así te lo pidieron, tal vez a ti se te ocurrió porque sientes que se ve mejor así. no importa, tu lo cambias. entonces ahí es cuando se presenta el shadowing.

const exerciseRoutine = {
    description: 'pushups',
    weekDays: [1,3,5]
}

/**
 * Este código salva una rutina de ejercicio para un usuario
 * @param {*} inputRoutine 
 */
function saveRoutine (exerciseRoutine) {
    console.log('exerciseRoutine = ', exerciseRoutine)
    
    //TODO: Save routine to database
}

const exampleInputRoutine = {
    description: 'situps',
    weekDays: [1,2,3,4,5]
}
saveRoutine(exampleInputRoutine)

Como puedes ver ahora cuando mandamos a llamar la variable de exerciseRoutine, se hace referencia a la variable local de la función, en lugar de a la global.

exerciseRoutine =  { description: 'situps', weekDays: [ 1, 2, 3, 4, 5 ] }

El problema viene con lo siguiente… Ahora ¿cómo accedes al valor de la variable global?

Al menos dentro de la función saveRoutine ya no puedes.

La referencia a la variable global exerciseRoutine se ha vuelto inaccesible ya que cuenta con una variable local que se llama igual. Podrías decir que la variable local lanzó una sombra que oculta la referencia a la variable global. De ahí que se le haya puesto al concepto: shadowing.

Diagrama circular de color rojo llamado Global Scope con 2 variables: exerciseRoutine y saveRoutine. También contiene un localScope(saveRoutine) que a su vez tiene otra variable llamada exerciseRoutine.

BONUS

Ahora la verdad es que hace un momento te mentí. Si hay una forma de acceder a una variable después de haberle hecho shadowing…de hecho hay varias.

El tema es que solo funcionan en ciertos escenarios:

  1. Necesitas haber declarado la variable con var o haber creado una función explícitamente con function.
  2. Necesitas una keyword específica que varía dependiendo del ambiente de Javascript que estés usando.
  3. Necesitas haber declarado la variable de manera global. Esto no funciona para shadowing en variables locales o de bloque.

Entre las keywords que podrías usar, se encuentran:

window

  • Debes de estar corriendo el código Javascript en un navegador
window.exerciseRoutine

self

  • Debes de estar corriendo el código Javascript en un Web Worker
self.exerciseRoutine

global

  • Debes de estar corriendo el código Javascript en Node.js
global.exerciseRoutine

globalThis

  • Debes de estar utilizando ES2020 o superior
globalThis.exerciseRoutine

El tema del shadowing es en esencia muy simple. Sin embargo si empiezas a considerar que los comportamientos cambian si declaraste una variable con let/const vs haberla declarado con var, y que hay diferentes formas de manejarlo dependiendo del ambiente…entonces si se va haciendo más complejo.

Al final la regla sigue siendo la misma. Evita hacer shadowing. No es buena práctica, no es claro y te ahorrará muchos dolores de cabeza.